package com.hang.jvm.risc.v2_memory_bus;

import cn.hutool.core.util.ArrayUtil;
import com.hang.jvm.risc.CPU;
import com.hang.jvm.risc.v2_memory_bus.exception.InvalidRegisterException;
import com.hang.jvm.risc.v2_memory_bus.exception.LoadAccessFaultException;
import lombok.Setter;

import static com.hang.jvm.risc.v2_memory_bus.Param.DRAM_BASE;
import static com.hang.jvm.risc.v2_memory_bus.Param.DRAM_END;

public class Cpu implements CPU {

    private final RegisterFile registerFile;

    @Setter
    private long pc = 0;

    private final Bus bus;

    private static final String[] RVABI = {"zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4",
            "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4",
            "t5", "t6"};

    /**
     * 读取寄存器的值
     *
     * @param r 寄存器名称
     * @return 寄存器的值
     * @throws InvalidRegisterException 不存在该寄存器
     */
    public long reg(String r) throws InvalidRegisterException {
        int index = ArrayUtil.indexOf(RVABI, r);
        if (index >= 0 && index <= 31) {
            return registerFile.get(index);
        } else if (r.startsWith("x")) {
            index = Integer.parseInt(r.substring(1));
            if (index >= 0 && index <= 31) {
                return registerFile.get(index);
            }
        } else if ("pc".equals(r)) {
            return this.pc;
        } else if ("fp".equals(r)) {
            return reg("s0");
        }
        throw new InvalidRegisterException(r);
    }

    public Cpu(byte[] code) {
        // 初始化寄存器组
        this.registerFile = new RegisterFile();
        this.registerFile.set(2, DRAM_END);
        // 初始化PC
        this.pc = DRAM_BASE;
        // 初始化总线
        this.bus = new Bus(code);
    }

    public long fetch() throws LoadAccessFaultException {
        return this.bus.load(this.pc, 32);
    }

    /**
     * 执行指令
     *
     * @param inst 指令
     * @return pc
     */
    public long execute(long inst) throws LoadAccessFaultException {

        int opcode = (int) (inst) & 0x0000007f;
        int rd = ((int) inst & 0x00000f80) >> 7;
        int rs1 = (int) (inst & 0x000f8000) >> 15;
        int rs2 = (int) (inst & 0x01f00000) >> 20;
        int funct3 = ((int) inst & 0x00007000) >> 12;
        int funct7 = ((int) inst & 0xfe000000) >> 25;
        int imm = ((int) inst & 0xfff00000) >> 20;

        long nextPc = pc + 4;
        // 所有指令的操作码低2位，0-1位都位11
        // 所有指令的操作码中间3位，2-4位不为111
        switch (opcode) {
            case 0B0000011: // 00_000_11
                // load
                int immITypeLoad = ((int) inst & 0xfff00000) >> 20;
                long addr = this.registerFile.get(rs1) + immITypeLoad;
                switch (funct3) {
                    case 0B000: // LB: Load Byte，从内存加载一个字节，8位有符号扩展到64位
                        long valLB = (byte) (this.load(addr, 8) & 0xff);
                        this.registerFile.set(rd, valLB);
                        nextPc = updatePc();
                        break;
                    case 0B100: // LBU: Load Byte，从内存加载一个字节，8位无符号扩展到64位
                        long valLBU = this.load(addr, 8);
                        this.registerFile.set(rd, valLBU);
                        nextPc = updatePc();
                        break;
                    default:
                        throw new UnsupportedOperationException("Unknown opcode: " + opcode);
                }
                break;
            case 0B0100011: // 01_000_11
                // store
                int immSTypeStore = (((int) inst & 0xfe000000) >> 20) | (((int) inst & 0xfff) >> 7);
                long addrStore = this.registerFile.get(rs1) + immSTypeStore;
                switch (funct3) {
                    case 0B000:
                        // SB: Store Byte to memory，8位
                        this.store(addrStore, 8, this.registerFile.get(rs2));
                        nextPc = updatePc();
                        break;
                    case 0B001:
                        // SH: Store Half word to memory，16位
                        this.store(addrStore, 16, this.registerFile.get(rs2));
                        nextPc = updatePc();
                        break;
                    case 0B010:
                        // SW: Store Word to memory，32位
                        this.store(addrStore, 32, this.registerFile.get(rs2));
                        nextPc = updatePc();
                        break;
                    case 0B011:
                        // SD: Store Double word to memory，64位
                        this.store(addrStore, 64, this.registerFile.get(rs2));
                        nextPc = updatePc();
                        break;
                    default:
                        throw new UnsupportedOperationException("Unknown opcode: " + opcode);
                }
                break;
            case 0B0010011: // 00_100_11
                // 0x13 立即数运算
                // imm[11:0] = inst[31:20]，立即数在高12位
                // 12位立即数有符号扩展，立即数在指令高20~31位，算术右移20位，高位填符号位的值
                long immIType = ((int) inst & 0xfff00000) >> 20;
                // 12位立即数无符号扩展，立即数在指令高20~31位，逻辑右移20位，高位填0
                long immITypeUnsigned = ((int) inst & 0xfff00000) >>> 20;
                // the shift amount is encoded in the lower 6 bits of the I-immediate field for RV64I
                // RV64I的寄存器是64位，2^6=64，所以shamt占6位
                int shamt = (int) immITypeUnsigned & 0x3f;

                switch (funct3) {
                    case 0B000:
                        // addi
                        long result = registerFile.get(rs1) + immIType;
                        registerFile.set(rd, result);
                        nextPc = updatePc();
                        break;
                    case 0B001:
                        // SLLI: Shift Left Logical Immediate
                        registerFile.set(rd, registerFile.get(rs1) << shamt);
                        nextPc = updatePc();
                        break;
                    case 0B010:
                        // SLTI: Set Less Than Immediate
                        // rd <- signed(rs1) < signed(imm) ? 1 : 0
                        // 将 rs1 64位二进制视为有符号数，将12位立即数进行有符号扩展到64位，
                        // 然后比较 rs1 的值是否小于 imm 立即数，如果小于，则将 rd 的值设置为1，否则将 rd 的值设置为0
                        registerFile.set(rd, registerFile.get(rs1) < immIType ? 1 : 0);
                        nextPc = updatePc();
                        break;
                    case 0B011:
                        // SLTIU: Set Less Than Immediate Unsigned
                        // rd <- unsigned(rs1) < unsigned(imm) ? 1 : 0
                        // 将 rs1 64位二进制视为无符号数，将12位立即数进行无符号扩展到64位，
                        // 然后比较 rs1 的值是否小于 imm 立即数，如果小于，则将 rd 的值设置为1，否则将 rd 的值设置为0
                        // 直接将符号位置0保证rs1为无符号
                        registerFile.set(rd, (registerFile.get(rs1) & 0x7fffffffffffffffL) < immITypeUnsigned ? 1 : 0);
                        nextPc = updatePc();
                        break;
                    case 0B100:
                        // XORI
                        registerFile.set(rd, registerFile.get(rs1) ^ immIType);
                        nextPc = updatePc();
                        break;
                    case 0B101:
                        // 右移
                        // RV64I用高6位区分（RV32I用高7位区分）
                        switch ((int) inst >>> 26) {
                            case 0B000000:
                                // SRLI: Shift Right Logical Immediate
                                // 逻辑右移，空出的高位用0填充
                                registerFile.set(rd, registerFile.get(rs1) >>> shamt);
                                nextPc = updatePc();
                                break;
                            case 0B010000:
                                // SRAI: Shift Right Arithmetic Immediate
                                // 算术右移，空出的高位用符号位填充
                                registerFile.set(rd, registerFile.get(rs1) >> shamt);
                                nextPc = updatePc();
                                break;
                            default:
                                throw new UnsupportedOperationException("Unknown opcode: " + opcode);
                        }
                        break;
                    case 0B110:
                        // ORI
                        registerFile.set(rd, registerFile.get(rs1) | immIType);
                        nextPc = updatePc();
                        break;
                    case 0B111:
                        // ANDI
                        registerFile.set(rd, registerFile.get(rs1) & immIType);
                        nextPc = updatePc();
                        break;
                    default:
                        throw new UnsupportedOperationException("Unknown opcode: " + opcode);
                }
                break;
            case 0B0011011: // 00_110_11
                // 32位立即数运算
                // imm[11:0] = inst[31:20]，立即数在高12位
                // 12位立即数有符号扩展，立即数在指令高20~31位，算术右移20位，高位填符号位的值
                int immITypeWord = ((int) inst & 0xfff00000) >> 20;
                // 12位立即数无符号扩展，立即数在指令高20~31位，逻辑右移20位，高位填0
                int immITypeUnsignedWord = ((int) inst & 0xfff00000) >>> 20;
                // 2^5=32位，所以shamt占5位
                int shamtWord = (int) immITypeUnsignedWord & 0x1f;

                switch (funct3) {
                    case 0B000:
                        // ADDIW: ADD Immediate Word (32位)
                        // Word 32位rs1 + 32位立即数
                        registerFile.set(rd, registerFile.get(rs1) + immITypeWord);
                        nextPc = updatePc();
                        break;
                    case 0B001:
                        // SLLIW: Shift Left Logical Word (32位)
                        // Word 逻辑左移
                        registerFile.set(rd, registerFile.get(rs1) << shamtWord);
                        nextPc = updatePc();
                        break;
                    case 0B101:
                        // Word 右移
                        // 用指令高7位区分
                        switch (funct7) {
                            case 0B0000000:
                                // SRLIW: Shift Right Logical Immediate Word (32位)
                                // Word 逻辑右移，空出的高位用0填充
                                registerFile.set(rd, ((int) registerFile.get(rs1)) >>> shamtWord);
                                nextPc = updatePc();
                                break;
                            case 0B0100000:
                                // SRAIW: Shift Right Arithmetic Immediate (32位)
                                // Word 算术右移，空出的高位用符号位填充
                                registerFile.set(rd, ((int) registerFile.get(rs1)) >> shamtWord);
                                nextPc = updatePc();
                                break;
                            default:
                                throw new UnsupportedOperationException("Unknown opcode: " + opcode);
                        }
                        break;
                    default:
                        throw new UnsupportedOperationException("Unknown opcode: " + opcode);

                }
                break;
            case 0B0110011: // 01_100_11
                // add
                long val1 = registerFile.get(rs1);
                long val2 = registerFile.get(rs2);
                registerFile.set(rd, val1 + val2);
                nextPc = updatePc();
                break;
            case 0B0010111: // 00_101_11
                // AUIPC
                // 0x17
                // AUIPC: Add Upper Immediate to PC, rd = pc + imm
                // U(Upper)型指令，0~6位是opcode，7~11位是rd，
                // 指令的高20位，即inst[31:12]为立即数的高20位，低12位全部为0，所以将低12位置0，
                // 然后将32位立即数据有符号扩展到64位，immUTypeAuipc 类型为long，会自动做类型转换，
                // 汇编指令中的立即数据只给高20位即可，低12位去掉，比如
                // auipc a0, 42
                // 会将42左移12位后加上pc的值后，放入a0寄存器，即 a0 = pc + (42 << 12)
                // AUIPC 和 JALR 一起使用，可以跳转到pc的相对地址
                long immUTypeAuipc = (int) inst & 0xfffff000;
                this.registerFile.set(rd, immUTypeAuipc + this.pc);
                nextPc = updatePc();
                break;
            case 0B0110111: // 01_101_11
                // LUI
                // 0x37
                // LUI: Load Upper Immediate, rd = imm，
                // U(Upper)型指令，0~6位是opcode，7~11位是rd，
                // 指令的高20位，即inst[31:12]为立即数的高20位，低12位全部为0，所以将低12位置0，
                // 然后将32位立即数据有符号扩展到64位，immUTypeLui 类型为long，会自动做类型转换，
                // 汇编指令中的立即数据只给高20位即可，低12位去掉，比如
                // lui a0, 42
                // 会将42左移12位后，放入a0寄存器，即 a0 = 42 << 12
                long immUTypeLui = (int) inst & 0xfffff000;
                this.registerFile.set(rd, immUTypeLui);
                nextPc = updatePc();
                break;
            default:
                throw new UnsupportedOperationException("Unknown opcode: " + opcode);
        }

        return nextPc;
    }

    private long updatePc() {
        return this.pc + 4;
    }

    /**
     * 读取数据
     *
     * @param addr 地址
     * @param size 位数，只能是 8、16、32、64
     * @return 读取到的数据
     * @throws LoadAccessFaultException 读取异常
     */
    public long load(long addr, int size) throws LoadAccessFaultException {
        return this.bus.load(addr, size);
    }

    /**
     * 存储数据
     *
     * @param addr  地址
     * @param size  位数，只能是 8、16、32、64
     * @param value 要存储的数据
     */
    public void store(long addr, int size, long value) throws LoadAccessFaultException {
        this.bus.store(addr, size, value);
    }


    public void dumpRegisters() {
        System.out.printf("%s%n", "-----------------------------------registers------------------------------------");
        for (int i = 0; i < 32; i += 4) {
            System.out.printf("x%2s(%4s) = 0x%016X    x%2s(%4s) = 0x%016X    x%2s(%4s) = 0x%016X    x%2s(%4s) = 0x%016X%n", i, RVABI[i], registerFile.get(i), i + 1, RVABI[i + 1], registerFile.get(i + 1), i + 2, RVABI[i + 2], registerFile.get(i + 2), i + 3, RVABI[i + 3], registerFile.get(i + 3));
        }
    }

    public void dumpPc() {
        System.out.printf("%s%n", "-----------------------------------PC registers------------------------------------");
        System.out.printf("pc = 0x%016X", pc);
    }

}
